﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Engine.Dtd.Schema;
using System.Diagnostics;

namespace Engine.Sgml {
    /// <summary>
    /// Represents a name of an SGML element or attribute.
    /// </summary>
    [DebuggerNonUserCode]
    public class SName : IEquatable<SName> {
        internal static XmlNameTable NameTable { get; private set; }
        /// <summary>
        /// Gets a SName object that represents the empty name.
        /// </summary>
        public static SName Empty { get; private set; }
        internal static string EmptyString { get; private set; }
        internal static string Xmlns { get; private set; }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private static Dictionary<string, SName> buffer;

        static SName() {
            NameTable = new NameTable();
            EmptyString = NameTable.Add(string.Empty);
            Empty = new SName { Prefix = EmptyString, LocalName = EmptyString };
            buffer = new Dictionary<string, SName>();
            buffer.Add(EmptyString, Empty);
            Xmlns = SName.Add("xmlns").LocalName;
            }

        /// <summary>
        /// Gets a SName object form a specified name.
        /// </summary>
        /// <param name="name">A string that contains a SGML name.</param>
        /// <returns>A SName object.</returns>
        public static SName Get(string name) {
            SName value = null;
            if (buffer.TryGetValue(name, out value))
                return value;
            else
                return Add(name);
            }

        private static SName Add(string name) {
            Guard.ArgumentNotNullOrEmpty(name, "name");
            SName value = null;
            int colonIndex = name.IndexOf(Characters.EndNamePrefix);
            if (colonIndex > 0) {
                value = new SName {
                    Prefix = SName.NameTable.Add(name.Substring(0, colonIndex)),
                    LocalName = SName.NameTable.Add(name.Substring(colonIndex + 1))
                };
                }
            else
                value = new SName {
                    Prefix = SName.Empty.Prefix,
                    LocalName = SName.NameTable.Add(name),
                };
            buffer.Add(name, value);
            return value;
            }

        /// <summary>
        /// Gets the SName associated with the specified name.
        /// </summary>
        /// <param name="name">A string that represents a SName object.</param>
        /// <param name="value">
        /// When this method returns, contains the SName object associated with the specified
        /// name, if the name is found; otherwise null. This parameter is passed uninitialized.
        /// </param>
        /// <returns>true if the SName with the specified key has already been added; otherwise, false.</returns>
        public static bool TryGet(string name, out SName value) {
            if (string.IsNullOrEmpty(name)) {
                value = null;
                return false;
                }
            else
                return buffer.TryGetValue(name, out value);
            }

        /// <summary>
        /// Gets the namespace part of the fully qualified name.
        /// </summary>
        public string Prefix { get; private set; }

        /// <summary>
        /// Gets the local part of the name.
        /// </summary>
        public string LocalName { get; private set; }

        /// <summary>
        /// Returns a value indicating whether two instances of SName are equal.
        /// </summary>
        /// <param name="first">The first SName object to compare.</param>
        /// <param name="second">The second SName object to compare.</param>
        /// <returns>true if the <paramref name="first"/> and <paramref name="second"/> parameters represent the same SName object, otherwise, false.</returns>
        public static bool operator ==(SName first, SName second) {
            if (object.ReferenceEquals(first, second))
                return true;
            if (object.Equals(first, null) || object.Equals(second, null))
                return false;
            else
                return object.ReferenceEquals(first.LocalName, second.LocalName) && object.ReferenceEquals(first.Prefix, second.Prefix);
            }

        /// <summary>
        /// Returns a value indicating whether two instances of SName are not equal.
        /// </summary>
        /// <param name="first">The first SName object to compare.</param>
        /// <param name="second">The second SName object to compare.</param>
        /// <returns>true if the <paramref name="first"/> and <paramref name="second"/> parameters represent different SName objects, otherwise, false.</returns>
        public static bool operator !=(SName first, SName second) {
            return !(first == second);
            }

        /// <summary>
        /// Converts a SName to a string object.
        /// </summary>
        /// <param name="name">A SName object to convert.</param>
        /// <returns>A string that represent this object.</returns>
        public static implicit operator System.String(SName name) {
            if (object.ReferenceEquals(name, null))
                return null;
            else
                return name.ToString();
            }

        /// <summary>
        /// Converts a string to a SName object.
        /// </summary>
        /// <param name="name">A string to convert.</param>
        /// <returns>A SName object.</returns>
        public static implicit operator SName(string name) {
            Guard.ArgumentNotNullOrWhitespace(name, "name");
            return SName.Get(name);
            }

        /// <summary>
        /// Determines whether this SName and the specified SName object are equal.
        /// </summary>
        /// <param name="other">An SName to compare to this instance.</param>
        /// <returns>true if <paramref name="other"/> is equal to the current SName instance; otherwise, false.</returns>
        public bool Equals(SName other) {
            if (object.ReferenceEquals(other, null))
                return false;
            return this == other;
            }

        /// <summary>
        /// Determines whether this SName and the specified object are equal.
        /// </summary>
        /// <param name="obj">An object to compare to this instance.</param>
        /// <returns>true if <paramref name="obj"/> is a SName and equal to the current SName instance; otherwise, false.</returns>
        public override bool Equals(object obj) {
            return Equals(obj as SName);
            }

        /// <summary>
        /// Returns the hash code for this SName object.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        public override int GetHashCode() {
            return LocalName.GetHashCode() ^ Prefix.GetHashCode();
            }

        /// <summary>
        /// Returns a string that represents this SName object.
        /// </summary>
        /// <returns>A string that represents this instance.</returns>
        public override string ToString() {
            if (object.ReferenceEquals(Prefix, SName.EmptyString))
                return LocalName;
            else
                return string.Format("{0}:{1}", Prefix, LocalName);
            }
        }
    }
